<?php

// FIXME : 2007-11-16 : lucky
// !!не используется!!
//
// концепт принципала в стиле PAM
//
// сам Principal до безобразия прост.
// но код модулей получается слишком путанным
//
// не решена проблема с получением данных от юзера для авторизации.
// в оригинальном PAM юзер вводит данные в интерактиве
// а нам, по большей части, нужно работать с сессиями
//
// непонятно как залогинить юзера с нужными реквизитами
//
define( "PRINCIPAL_ERR",                255 );
define( "PRINCIPAL_OK",                 0  );
define( "PRINCIPAL_AUTH",               PRINCIPAL_OK);

define( "PRINCIPAL_CRED_INSUFFICIENT",  1  );
define( "PRINCIPAL_USER_UNKNOWN",       2  );
define( "PRINCIPAL_AUTH_ERR",           4  );

define( "PRINCIPAL_NEW_AUTHTOK_REQD",   5  );

class RL_Principal
{

	public function __construct ($ctx, $storage_model=NULL, $security_models=NULL)
	{
		$this->rh = $ctx;
		$this->ctx = $ctx->ctx;

		//. достроить стораж модель
		$this->rh->useClass("RL_Principal_Storage");
		$this->storage_model = RL_Principal_Storage::Factory( $this, $storage_model );
		$this->storage_model->magic_word = $this->rh->magic_word;

		$this->config = array(

			'ask' => array(
				array('cookie', array('digest'), array(
					                                'digest' => $this->rh->cookie_prefix.'_principal_digest',
				)),
				array('request', array('openid_url', 'openid_action')
				),
				array('request', array('login', 'password'), array(
					                                'login'    => '_principal_login',
					                                'password' => '_principal_password',
				)),
			),

			'auth' => array(
				array('sufficient', 'store'),
				array('sufficient', 'openid', array(
					                                'login_url' => 'http://localhost:81/~lucky/platonica/www/z/login',
					                                'trust_url' => 'http://localhost:81/~lucky/platonica/www/',
				)),
				array('required',   'permit', array(
					                                 'login' => 'guest'
				)),
			),

			'account' => array(
				array('required', 'openid'),
				array('required', 'store'),
				array('required', 'permit'), 
			),

			'password' => array (
				array('required', 'store'),
				array('required', 'cookie',   array(
					                                'digest'      => $this->rh->cookie_prefix.'_principal_digest',
					                                'domain'      => $this->rh->cookie_domain,
					                                'expire_days' => $this->rh->cookie_expire_days,
				)),
			),

			'security' => array (
				array('sufficient', 'ip'),
				array('sufficient', 'role'),
			),

		);

	} // end of __construct
	public function identify ()
	{
		if (isset($this->token)) $this->logout();

		// создадим новый 
		$this->token = $token = principal_start(null, $this->config);
		principal_set_item($token, 'principal', $this);
		principal_set_item($token, 'store', $this->storage_model);

		$res = principal_identify($token);
		$this->data = principal_get_item($token, 'cred');

		return $res;
	} // end of function identify
	public function logout ()
	{
		$status = principal_end($this->token);
		unset($this->token);
		return $status;
	} // end of function logout

}

function principal_start ($service_name=NULL, $config)
{
	$token = new StdClass();
	$cred  = array();
	principal_set_item($token, 'cred', $cred);
	principal_set_item($token, 'service_name', isset($service_name) ? $service_name : 'system');
	principal_set_item($token, 'config', $config);
	return $token;
}
function principal_identify ($token)
{
	$res = principal_authenticate($token);
	if ($res === PRINCIPAL_OK) {
		$res = principal_account($token);
		if ($res === PRINCIPAL_NEW_AUTHTOK_REQD) {
			$res = principal_chauthtok($token);
		}
	}
	return $res;
} // end of function principal_identify
function principal_end   ($token)
{
	$res = principal_session_close($token);
	return $res;
}

function principal_ask ($token)
{
}

function principal_authenticate  ($token)
{
	return principal_chain($token, NULL, 'auth', 'authenticate');
}
function principal_account       ($token)
{
	return principal_chain($token, NULL, 'account', 'account');
}
function principal_chauthtok     ($token)
{
	return principal_chain($token, NULL, 'password', 'chauthtok');
}

function principal_set_item ($token, $name, $data)
{
	$old_value = $token->$name;
	$token->$name = $data;
	return $old_value;
}
function principal_get_item ($token, $name)
{
	$value = $token->$name;
	return $value;
}

// private
function principal_chain($token, $service_name, $facility, $action) 
{
	$res = PRINCIPAL_OK;
	$config = principal_get_item($token, 'config');
	/*
	if (!isset($service_name)) $service_name = principal_get_item($token, 'service_name');
	if (!isset($config[$service_name])) {
		$res = PRINCIPAL_ERR;
	} else {
	 */
		if (isset($config/*[$service_name]*/[$facility])) {
			foreach ($config/*[$service_name]*/[$facility] as $v) {
				switch (count($v)) {
				case 2:
					list($ctrl, $module) = $v;
					$args = array();
					break;
				case 3:
					list($ctrl, $module, $args) = $v;
					break;
				default:
					$res = PRINCIPAL_ERR;
					break 2;
				}

				$f      = $module.'_principal_'.$action;
				$status = $f($token, $args);
				switch ($ctrl) {
				case 'required':
					if ($status !== PRINCIPAL_OK && $res === PRINCIPAL_OK) {
						$res = $status;
					}
					break;
				case 'sufficient':
					if ($status === PRINCIPAL_OK && $res === PRINCIPAL_OK) {
						$res = $status;
						break 2;
					}
					break;
				case 'optional':
					break;
					/*
				case 'include':
					$res = principal_chain($token, $config, $module /* here service_name * /, $facility, $action);
					break;
					 */
				default:
					break;
				}
			}
		}
	/*
	}
	 */
	return $res;
}

// --- module/cookie --- {{{
function cookie_principal_ask($token, $args, &$resp)
{
	$resp = array(); 
	foreach ($args as $k=>$v) {
		$resp[$k] = $_COOKIE[$v];
	}

	return PRINCIPAL_OK;
}
function cookie_principal_confirm($token, $args)
{
	return PRINCIPAL_OK;
}
function cookie_principal_refute($token, $args)
{
	$cookie_domain      = isset($args[     'domain']) ? $args[     'domain'] :     NULL;
	$cookie_expire_days = isset($args['expire_days']) ? $args['expire_days'] :      123;

	foreach ($args as $k=>$v) {
		setcookie( $cookie_name, 
			'',
			time() - $cookie_expire_days * 86400, // seconds per day
			// 86400 = 24 h * 60 min * 60 sec
			"/",
			$cookie_domain); 
	}
}

function cookie_principal_chauthtok($token, $args)
{
	$digest = principal_get_item($token, 'digest');
	$cookie_name        = isset($args[     'digest']) ? $args[     'digest'] : 'digest';
	$cookie_domain      = isset($args[     'domain']) ? $args[     'domain'] :     NULL;
	$cookie_expire_days = isset($args['expire_days']) ? $args['expire_days'] :      123;

	if (isset($digest)) {
		setcookie( $cookie_name, 
							 $digest,
							 time() + $cookie_expire_days * 86400, // seconds per day
                                                     // 86400 = 24 h * 60 min * 60 sec
							 "/",
							 $cookie_domain); 
	} else if (isset($_COOKIE[$cookie_name])) {
		setcookie( $cookie_name, 
			'',
			time() - $cookie_expire_days * 86400, // seconds per day
			// 86400 = 24 h * 60 min * 60 sec
			"/",
			$cookie_domain); 
	}

	return PRINCIPAL_OK;
}
function cookie_principal_session_close($token, $args)
{
	$digest = principal_get_item($token, 'digest');
	$cookie_name        = isset($args[     'digest']) ? $args[     'digest'] : 'digest';
	$cookie_domain      = isset($args[     'domain']) ? $args[     'domain'] :     NULL;
	$cookie_expire_days = isset($args['expire_days']) ? $args['expire_days'] :      123;

	setcookie( $cookie_name, 
		'',
		time() - $cookie_expire_days * 86400, // seconds per day
		// 86400 = 24 h * 60 min * 60 sec
		"/",
		$cookie_domain); 
	return PRINCIPAL_OK;
}
// --- module/cookie --- }}}

// --- module/request --- {{{
function request_principal_session_close($token, $args)
{
	return PRINCIPAL_OK;
}
// --- module/request --- }}}

// --- module/store --- {{{
function store_principal_authenticate($token, $args)
{
	$status   = PRINCIPAL_USER_UNKNOWN;
	$store    = principal_get_item($token, 'store');
	do {
		if (!isset($store)) {
			break;
		}

		principal_ask($token, array('digest'));
		$digest = principal_get_item($token, 'digest');
		if (isset($digest)) {
			$data = $store->loadByDigest($digest);
			if ($data) {
				principal_set_item($token, 'store_data', $data);
				$status = PRINCIPAL_AUTH;
				break;
			} else { 
				principal_set_item($token, 'digest', NULL);
			}
		}

		principal_ask($token, array('login', 'password'));
		$login    = principal_get_item($token, 'login');
		$password = principal_get_item($token, 'password');
		if (isset($login) && isset($password)) {
			$data = $store->loadByLogin($login);
			if ($data) {
				if (store_check_password($store, $data, $password)) {
					principal_set_item($token, 'store_data', $data);
					$status = PRINCIPAL_AUTH;
				} else {
					$status = PRINCIPAL_AUTH_ERR;
				}
				break;
			}
		}

	} while (false);
	return $status;
}
function store_principal_account($token, $args)
{
	$status = PRINCIPAL_OK;
	$data   = principal_get_item($token, 'store_data');
	if (isset($data)) {
		$cred = principal_get_item($token, 'cred');
		$new_cred = array_merge($cred, $data);
		principal_set_item($token, 'cred', $new_cred);
	  $status = PRINCIPAL_OK;
	}
	return $status;
}
function store_principal_chauthtok($token, $args)
{
	$store = principal_get_item($token, 'store');
	$cred  = principal_get_item($token, 'cred');
	$cred  = store_update_credential($store, $cred);
	principal_set_item($token, 'cred', $cred);
	principal_set_item($token, 'digest', $cred['digest']);
	return PRINCIPAL_OK;
}

function store_generate_stored_invariant($store, $password)
{
	$magic_word = $store->magic_word; 
	$gen = $password.$magic_word;
	return md5($gen);
}
function store_check_password($store, $data, $password)
{
	$possible_invariant = store_generate_stored_invariant($store, $password);
	if ($possible_invariant === $data['stored_invariant']) {
		return TRUE;
	}
	return FALSE;
}
function store_update_credential($store, $data)
{
	$digest = store_generate_stored_invariant($store, $data['password']); 
	$data = $store->SetStoredDigest($data, $digest);
	$data['digest'] = $digest;
	return $data;
}
// --- module/store --- }}}

// --- module/openid --- {{{
require_once 'openid/class.openid.php';
function openid_principal_authenticate($token, $args)
{
	$status = PRINCIPAL_USER_UNKNOWN;

	$login_url = isset($args['login_url']) ? $args['login_url']              : NULL;
	$trust_url = isset($args['trust_url']) ? $args['trust_url']              : NULL;

	$openid = new SimpleOpenID();

	principal_ask($token, array('openid_action', 'openid_url'));

	$openid_action = principal_get_item($token, 'openid_action');
	$openid_url    = principal_get_item($token, 'openid_url');

	// EXAMPLE
	if ($openid_action == "login") { // Get identity from user and redirect browser to OpenID Server
		$openid->SetIdentity($openid_url);
		$openid->SetTrustRoot($trust_url);
		$openid->SetRequiredFields(array('email','fullname'));
		$openid->SetOptionalFields(array('dob','gender','postcode','country','language','timezone'));

		$approved_url = $login_url
				.(($_SERVER['HTTP_REFERER'] !== $login_url)
					? '?ret='.urlencode($_SERVER['HTTP_REFERER'])
					: ''
		);

		if ($openid->GetOpenIDServer()) {
			$openid->SetApprovedURL($approved_url);  	// Send Response from OpenID server to this script
			$openid->Redirect(); 	// This will redirect user to OpenID Server
			die();

		} else {
			$error = $openid->GetError();
			$status = PRINCIPAL_ERR;
			//echo "ERROR CODE: " . $error['code'] . "<br>";
			//echo "ERROR DESCRIPTION: " . $error['description'] . "<br>";
		}
		//exit;
	}
	else if ($_GET['openid_mode'] == 'id_res') { // Perform HTTP Request to OpenID server to validate key
		$openid->SetIdentity($_GET['openid_identity']);
		$openid_validation_result = $openid->ValidateWithServer();

		if ($openid_validation_result == true) { // OK HERE KEY IS VALID
			$user_login = $openid->OpenID_Standarize($_GET['openid_identity']);

			$data = $_GET;
			$data['login'] = $user_login;
			principal_set_item($token, 'openid_data', $data);
			$status = PRINCIPAL_AUTH;
		} else if ($openid->IsError() == true) {			// ON THE WAY, WE GOT SOME ERROR
			$error = $openid->GetError();
			//echo "ERROR CODE: " . $error['code'] . "<br>";
			//echo "ERROR DESCRIPTION: " . $error['description'] . "<br>";
			$status = PRINCIPAL_ERR;
		} else {											// Signature Verification Failed
			//echo "INVALID AUTHORIZATION";
			$status = PRINCIPAL_AUTH_ERR;
		}
	} else if ($_GET['openid_mode'] == 'cancel'){ // User Canceled your Request
		$status = PRINCIPAL_USER_UNKNOWN;
		//echo "USER CANCELED REQUEST";
	}

	return $status;
}
function openid_principal_account($token, $args)
{
	$status = PRINCIPAL_OK;
	$openid_data = principal_get_item($token, 'openid_data');
	if ($openid_data) {
		$login = $openid_data['login']; 
		principal_set_item($token, 'login', $login);
		$cred = principal_get_item($token, 'cred');
		$cred[   'login'] = $login;
		$cred['password'] = time().'123';
		$cred['fullname'] = $openid_data['openid_sreg_fullname'];
		$cred[   'email'] = $openid_data['openid_sreg_email'];
		principal_set_item($token, 'cred', $cred);
		$status = PRINCIPAL_NEW_AUTHTOK_REQD;
	}
	return $status;
}
// --- module/openid --- }}}

// --- module/permit --- {{{
function permit_principal_authenticate($token, $args)
{
	$login = isset($args['login']) ? $args['login'] : 'guest';
	principal_set_item($token, 'login', $login);
	return PRINCIPAL_OK;
}
function permit_principal_account($token, $args)
{
	return PRINCIPAL_OK;
}
// --- module/permit --- }}}

